package de.justphil.tcg.tcgserver.rest.db;

import static de.justphil.tcg.tcgserver.commons.logging.TCGLogging.restLogger;

import javax.ws.rs.core.Response;

import org.slf4j.Logger;

import com.hazelcast.core.Transaction;

import de.justphil.tcg.tcgserver.commons.domain.Card;
import de.justphil.tcg.tcgserver.commons.domain.CardInstance;
import de.justphil.tcg.tcgserver.commons.domain.Deck;
import de.justphil.tcg.tcgserver.commons.domain.User;
import de.justphil.tcg.tcgserver.commons.exceptions.TCGException;
import de.justphil.tcg.tcgserver.commons.grid.Grid;
import de.justphil.tcg.tcgserver.commons.i18n.Error;
import de.justphil.tcg.tcgserver.commons.management.DataManager;

public class DeckExtinguisher {
	private static final Logger log = restLogger(DeckExtinguisher.class);
	
	public Deck deleteDeck(DataManager dataManager, User user, long deckId) throws TCGException {
		
		log.debug("deleteDeck()");
		
		// Begin Hazelcast transaction
		Transaction txn = Grid.getTransaction();
		txn.begin();
		
		try {
			
			Deck deletedDeck = doDeleteDeck(dataManager, user, deckId);
			
			// Commit transactions
			txn.commit();
			
			return deletedDeck;
			
		}
		catch (TCGException e) {
			// Expected exception
			
			// Rollback Hazelcast transaction
			txn.rollback();
			
			// Rethrow this exception
			throw e;
			
		}
		catch (Throwable t) {
			// Unexpected exception
			
			// Print message and stack trace
			log.error("deleteDeck() ## Unexpected Throwable: {}", t.getMessage());
			
			t.printStackTrace();
			
			// Rollback Hazelcast transaction
			txn.rollback();
			
			// Throw TCGException
			throw new TCGException(Error.SERVER_ERROR_INTERNAL, Response.Status.INTERNAL_SERVER_ERROR);
			
		}
		
	}

	private Deck doDeleteDeck(DataManager dataManager, User user, long deckId) throws TCGException {
		
		Deck deletedDeck = dataManager.getDeckById(deckId);
		
		// Remove deck from "decks" IMap
		dataManager.deleteDeck(deckId);
		
		// Remove deck from "users" IMap
		user.getDecks().remove(deletedDeck);
		
		// Update all references to the deleted deck within the corresponding card instances
		
		for (CardInstance ci : deletedDeck.getCardInstances()) {
			
			ci.getDecks().remove(deletedDeck.getId());
			
			// "users" IMap - card instances
			if (user.getCardInstances().remove(ci)) {
				user.getCardInstances().add(ci);
				log.debug("Removing deck {} from user's card instance {}.", deckId, ci.getId());
				// the update invocation is below (just before the 'return' statement)
			}
			
			// "decks" IMap - card instances
			for (Long anotherDeckId : ci.getDecks()) {
				Deck anotherDeck = getUserDeckById(user, anotherDeckId.longValue());
				if (anotherDeck != null && anotherDeck.getCardInstances().remove(ci)) {
					anotherDeck.getCardInstances().add(ci);
					log.debug("Removing deck {} from user decks's card instance {}.", deckId, ci.getId());
				}
			}
			
			
			// "cards" IMap - card instances
			Card card = dataManager.getCardById(ci.getCardId());
			if (card.getCardInstances().remove(ci)) {
				card.getCardInstances().add(ci);
				log.debug("Removing deck {} from card's card instance {}.", deckId, ci.getId());
			}
			dataManager.updateCard(card.getId(), card);
			
			// "cardInstances" IMap - card instances
			dataManager.updateCardInstance(ci.getId(), ci);
			log.debug("Removing deck {} from card instance {}.", deckId, ci.getId());
		}
		
		dataManager.updateUser(user.getId(), user);
		
		return deletedDeck;
		
	}
	
	private Deck getUserDeckById(User user, long deckId) {
		for (Deck d : user.getDecks()) {
			if (d.getId() == deckId) {
				return d;
			}
		}
		
		return null;
	}
}
